package cn.bossfriday.im.common.enums.api;

import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * ApiTrieTree
 *
 * @author chenx
 */
public class ApiTrieTree {

    /**
     * TrieNode
     */
    static class TrieNode {

        Map<String, TrieNode> fixedChildren = new HashMap<>();

        Map<String, TrieNode> variableChildrenMap = new HashMap<>();

        ApiRequestType apiRequestType;

        String variableName;
    }

    private final Map<String, TrieNode> methodTrieNodeMap = new HashMap<>();

    /**
     * insert
     *
     * @param type
     */
    public void insert(ApiRequestType type) {
        TrieNode root = this.methodTrieNodeMap.computeIfAbsent(type.getHttpMethod(), k -> new TrieNode());

        // 去掉首空字符串 ""
        String[] segments = Arrays.stream(type.getUrlParser().getPatternSegments())
                .filter(s -> !s.isEmpty())
                .toArray(String[]::new);

        TrieNode node = root;
        for (String seg : segments) {
            if (this.isVariable(seg)) {

                // 如果同一级变量节点已存在，直接复用
                node = node.variableChildrenMap.computeIfAbsent(seg, k -> {
                    TrieNode newNode = new TrieNode();
                    newNode.variableName = seg;

                    return newNode;
                });
            } else {
                node = node.fixedChildren.computeIfAbsent(seg, k -> new TrieNode());
            }
        }

        node.apiRequestType = type;
    }

    /**
     * find
     *
     * @param method
     * @param uri
     * @return
     */
    public ApiRequestType find(String method, URI uri) {
        TrieNode root = this.methodTrieNodeMap.get(method);
        if (Objects.isNull(root)) {
            return null;
        }

        // 去掉首空字符串 ""
        String[] segments = Arrays.stream(uri.getPath().split("/"))
                .filter(s -> !s.isEmpty())
                .toArray(String[]::new);

        return this.match(root, segments, 0);
    }

    /**
     * match(递归匹配)
     */
    private ApiRequestType match(TrieNode node, String[] segments, int index) {
        if (Objects.isNull(node)) {
            return null;
        }
        if (index == segments.length) {
            return node.apiRequestType;
        }

        String seg = segments[index];

        // 优先匹配固定路径
        TrieNode next = node.fixedChildren.get(seg);
        if (!Objects.isNull(next)) {
            ApiRequestType result = this.match(next, segments, index + 1);
            if (!Objects.isNull(result)) {
                return result;
            }
        }

        /**
         * 尝试变量节点: 循环次数极少（通常 = 1）
         * POST
         *  └─ api
         *      └─ {apiVersion}  <-- 唯一节点
         *          ├─ client/nav
         *          └─ user/getToken
         */
        for (TrieNode varNode : node.variableChildrenMap.values()) {
            ApiRequestType result = this.match(varNode, segments, index + 1);
            if (!Objects.isNull(result)) {
                return result;
            }
        }

        return null;
    }

    /**
     * isVariable
     */
    private boolean isVariable(String seg) {
        return seg.startsWith("{") && seg.endsWith("}");
    }
}
